/**
 * Copyright (C) 2009 Matteo Merli <mm@merlimat.org>
 * 
 * $Id: ConversionFactory.java 21 2009-01-12 17:30:19Z matteo.merli $
 * $URL: http://pxl-lang.googlecode.com/svn/trunk/src/main/old_code/ConversionFactory.java $
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pxl.types;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import pxl.types.util.PxlArgument;

import com.google.common.collect.Maps;

/**
 * Utility methods to convert 'to' and 'from' Java and Pxl environments. The
 * methods are able to convert all the basic data types into the corresponding
 * ones. If no coversion is possible (for example for a custom Java class), will
 * be created a JavaObject that embeds the original object instance. Container
 * are recursed to convert also the contained objects.
 */
public final class ConversionFactory
{

    public static Object convertToJava( PxlObject pxlObject )
    {
        return convertToJava( pxlObject, (Class<?>) null );
    }

    public static Object convertToJava( PxlObject pxlObject, Class<?> destClass )
    {
        if ( pxlObject instanceof None )
            return null;
        if ( pxlObject instanceof PxlString )
            return ((PxlString) pxlObject).getValue();
        if ( pxlObject instanceof PxlInteger )
        {
            if ( destClass != null )
            {
                if ( destClass == Integer.class || destClass == Integer.TYPE )
                {
                    return (int) ((PxlInteger) pxlObject).getValue();
                }
            }
            return ((PxlInteger) pxlObject).getValue();
        }
        if ( pxlObject instanceof PxlFloat )
        {
            if ( destClass != null )
            {
                if ( destClass == Float.class || destClass == Float.TYPE )
                {
                    return (float) ((PxlFloat) pxlObject).getValue();
                }
            }
            return ((PxlFloat) pxlObject).getValue();
        }
        if ( pxlObject instanceof PxlBoolean )
            return ((PxlBoolean) pxlObject).getValue();
        if ( pxlObject instanceof PxlList )
            return convertListToJava( (PxlList) pxlObject );
        if ( pxlObject instanceof PxlMap )
            return convertMapToJava( (PxlMap) pxlObject );

        if ( pxlObject instanceof JavaObject )
            return ((JavaObject) pxlObject).getObject();
        if ( pxlObject instanceof JavaClass )
            return ((JavaClass) pxlObject).getInternalClass();
        if ( pxlObject instanceof JavaMethod )
            return ((JavaMethod) pxlObject).getInternalMethod();

        return null;
    }

    public static PxlObject convertFromJava( Object obj )
    {
        return convertFromJava( obj, null );
    }

    /**
     * Convert Java object to primitive Pxl types, or encapsulate them inside a
     * JavaObject container.
     */
    @SuppressWarnings ( "unchecked")
    public static PxlObject convertFromJava( Object obj, JavaClass objClass )
    {
        if ( obj == null )
            return None.instance();

        if ( obj instanceof PxlObject )
            return (PxlObject) obj;

        if ( obj instanceof String )
            return new PxlString( (String) obj );
        if ( obj instanceof Integer )
            return new PxlInteger( ((Integer) obj).longValue() );
        if ( obj instanceof Long )
            return new PxlInteger( ((Long) obj).longValue() );
        if ( obj instanceof Float )
            return new PxlFloat( ((Float) obj).floatValue() );
        if ( obj instanceof Double )
            return new PxlFloat( ((Double) obj).doubleValue() );
        if ( obj instanceof Boolean )
            return new PxlBoolean( ((Boolean) obj).booleanValue() );
        if ( obj.getClass().isArray() )
            return convertArrayFromJava( (Object[]) obj );
        if ( obj instanceof List )
            return convertListFromJava( (List) obj );
        if ( obj instanceof Map )
            return convertMapFromJava( (Map) obj );

        // obj is not a Pxl primitive type
        if ( objClass == null )
            objClass = JavaClassFactory.getInstance().getClassByName(
                    obj.getClass().getName() );

        return new JavaObject( objClass, obj );
    }

    private static PxlObject convertArrayFromJava( Object[] items )
    {
        PxlList pxlList = new PxlList( items.length );

        for ( Object obj : items )
            pxlList.add( convertFromJava( obj ) );

        return pxlList;
    }

    private static PxlObject convertListFromJava( List<Object> list )
    {
        PxlList pxlList = new PxlList( list.size() );

        for ( Object obj : list )
            pxlList.add( convertFromJava( obj ) );

        return pxlList;
    }

    @SuppressWarnings ( "unchecked")
    private static List convertListToJava( PxlList pxlList )
    {
        List list = new ArrayList( pxlList.getValue().size() );

        PxlIterator it = pxlList.__getiterator__();
        PxlObject pxlObj;

        while ( it.hasNext() == PxlBoolean.True )
        {
            pxlObj = it.next();
            list.add( convertToJava( pxlObj ) );
        }

        return list;
    }

    private static PxlObject convertMapFromJava( Map<Object, Object> map )
    {
        PxlMap pxlMap = new PxlMap( map.size() );

        for ( Entry<Object, Object> entry : map.entrySet() )
        {
            pxlMap.put( convertFromJava( entry.getKey() ),
                    convertFromJava( entry.getValue() ) );
        }

        return pxlMap;
    }

    @SuppressWarnings ( "unchecked")
    private static Map convertMapToJava( PxlMap pxlMap )
    {
        Map<Object, Object> map = Maps.newHashMapWithExpectedSize( pxlMap
                .getValue().size() );
        PxlObject key, value;

        for ( Entry<PxlObject, PxlObject> entry : pxlMap.getValue().entrySet() )
        {
            key = entry.getKey();
            value = entry.getValue();

            map.put( convertToJava( key ), convertToJava( value ) );
        }

        return map;
    }

    protected static Object[] convertArgsToJava( List<PxlArgument> list )
    {
        int n = list.size();
        Object[] args = new Object[n];

        for ( int i = 0; i < n; i++ )
            args[i] = convertToJava( list.get( i ).value() );

        return args;
    }

    protected static Object[] convertArgsToJava( List<PxlArgument> list,
            Class<?>[] parameterTypes )
    {
        Object[] args = new Object[list.size()];

        for ( int i = list.size() - 1; i >= 0; i-- )
            args[i] = convertToJava( list.get( i ).value(), parameterTypes[i] );

        return args;
    }

}
